﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows.Threading;
using Bookvar.ObjectModel;
using Bookvar.ObjectModel.Collections;
using Bookvar.ServiceModel;

namespace Bookvar.Common
{
	public class FileSystemAutoSaveRecoveryService : IAutoSaveRecoveryService
	{
		private readonly DispatcherTimer timer;

		private static readonly string userAutoSaveFilePath = Path.Combine( FileUtility.BookvarLocalApplicationFolder,
		                                                                    "AutosavedMaps.xml" );

		private readonly Dictionary<Guid, MindMap> mapsToAutoSave = new Dictionary<Guid, MindMap>();

		public FileSystemAutoSaveRecoveryService() : this( 1 )
		{
		}

		public FileSystemAutoSaveRecoveryService( int intervalInMinutes )
		{
			timer = new DispatcherTimer { Interval = TimeSpan.FromMinutes( intervalInMinutes ) };
			timer.Tick += OnTimerTicked;
			timer.Start();
		}

		#region IAutoSaveRecoveryService Members

		public event EventHandler<AutoSavingEventArgs> AutoSaving;

		public void AddMapForAutoSave( MindMap map )
		{
			if ( !mapsToAutoSave.ContainsKey( map.Key ) )
			{
				mapsToAutoSave.Add( map.Key, map );
			}
		}

		public void RemoveMapForAutoSave( MindMap map )
		{
			if ( mapsToAutoSave.ContainsKey( map.Key ) )
			{
				if ( File.Exists( GetMapAutoSaveFileName( map.Key ) ) )
				{
					File.Delete( GetMapAutoSaveFileName( map.Key ) );
				}
				RemoveMapFromAutoSaveRegister( map );
				mapsToAutoSave.Remove( map.Key );
			}
		}

		public void ResetMapAutoSave( MindMap map )
		{
			RemoveMapForAutoSave( map );
			AddMapForAutoSave( map );
		}

		public bool IsMapRecoverable( Guid mapKey )
		{
			string mapPath = GetMapAutoSaveFileName( mapKey );
			return File.Exists( mapPath );
		}

		public void RecoverMapTo( Guid mapKey, string pathToRecoverTo )
		{
			if ( IsMapRecoverable( mapKey ) )
			{
				Mutex mutex = new Mutex( false, "Recovering map file" );
				mutex.WaitOne();
				if ( File.Exists( pathToRecoverTo ) )
				{
					File.Delete( pathToRecoverTo );
				}
				File.Move( GetMapAutoSaveFileName( mapKey ), pathToRecoverTo );
				mutex.ReleaseMutex();
			}
		}

		public RecoveredMapInfoCollection GetAutoSavedMaps()
		{
			RecoveredMapInfoCollection autosavedMaps = new RecoveredMapInfoCollection();
			Mutex mutex = new Mutex( false, "Autosaved maps file" );
			mutex.WaitOne();
			if ( File.Exists( userAutoSaveFilePath ) )
			{
				string autosavedFilesText = File.ReadAllText( userAutoSaveFilePath );
				if ( !String.IsNullOrEmpty( autosavedFilesText ) )
				{
					autosavedMaps = Serializer.Deserialize<RecoveredMapInfoCollection>( autosavedFilesText );
				}
			}

			mutex.ReleaseMutex();
			return autosavedMaps;
		}

		public void ClearAutoSavedMaps()
		{
			Mutex mutex = new Mutex( false, "Autosaved maps file" );
			mutex.WaitOne();
			if ( File.Exists( userAutoSaveFilePath ) )
			{
				File.Delete( userAutoSaveFilePath );
			}
			mutex.ReleaseMutex();
		}

		#endregion

		private void OnTimerTicked( object sender, EventArgs e )
		{
			AutoSaveMaps();
		}

		private void AutoSaveMaps()
		{
			foreach ( MindMap mapToAutoSave in mapsToAutoSave.Values )
			{
				OnAutoSaving( new AutoSavingEventArgs( mapToAutoSave ) );
				string tempFile = Path.GetTempFileName();
				//HACK: FileName should be persisted after auto saving.
				string originalFileName = mapToAutoSave.FileName;
				FileSystemPersister persister = new FileSystemPersister( mapToAutoSave, tempFile );
				mapToAutoSave.FileName = originalFileName;
				if ( persister.Save() )
				{
					string autosaveFileName = GetMapAutoSaveFileName( mapToAutoSave.Key );
					if ( File.Exists( autosaveFileName ) )
					{
						File.Delete( autosaveFileName );
					}
					File.Move( tempFile, autosaveFileName );
					AddMapToAutoSaveRegister( mapToAutoSave );
				}

				persister.ClearStore();
				if ( File.Exists( tempFile ) )
				{
					File.Delete( tempFile );
				}
			}
		}

		private static void AddMapToAutoSaveRegister( MindMap map )
		{
			Mutex mutex = new Mutex( false, "Autosaved maps file" );
			mutex.WaitOne();
			if ( !File.Exists( userAutoSaveFilePath ) )
			{
				File.Create( userAutoSaveFilePath ).Close();
			}
			string autosavedFilesText = File.ReadAllText( userAutoSaveFilePath );
			RecoveredMapInfoCollection autosavedMaps = new RecoveredMapInfoCollection();
			if ( !String.IsNullOrEmpty( autosavedFilesText ) )
			{
				autosavedMaps = Serializer.Deserialize<RecoveredMapInfoCollection>( autosavedFilesText );
			}
			if ( autosavedMaps != null && !autosavedMaps.Contains( map.Key ) )
			{
				RecoveredMapInfo mapInfo = new RecoveredMapInfo();
				mapInfo.Key = map.Key;
				mapInfo.FileName = map.FullPath;
				mapInfo.Name = map.FileName;
				if ( string.IsNullOrEmpty( mapInfo.FileName ) || mapInfo.FileName == MindMap.MindmapDefaultFileName )
				{
					mapInfo.FileName = String.Empty;
					mapInfo.Name = map.MainTopic.Name;
				}
				autosavedMaps.Add( mapInfo );
				File.WriteAllText( userAutoSaveFilePath, Serializer.Serialize( autosavedMaps ) );
			}

			mutex.ReleaseMutex();
		}

		private static void RemoveMapFromAutoSaveRegister( MindMap map )
		{
			Mutex mutex = new Mutex( false, "Autosaved maps file" );
			mutex.WaitOne();
			if ( File.Exists( userAutoSaveFilePath ) )
			{
				string autosavedFilesText = File.ReadAllText( userAutoSaveFilePath );
				RecoveredMapInfoCollection autosavedMaps = new RecoveredMapInfoCollection();
				if ( !String.IsNullOrEmpty( autosavedFilesText ) )
				{
					autosavedMaps = Serializer.Deserialize<RecoveredMapInfoCollection>( autosavedFilesText );
				}
				if ( autosavedMaps.Contains( map.Key ) )
				{
					autosavedMaps.Remove( map.Key );
					File.WriteAllText( userAutoSaveFilePath, Serializer.Serialize( autosavedMaps ) );
				}
			}
			mutex.ReleaseMutex();
		}

		private static string GetMapAutoSaveFileName( Guid mapKey )
		{
		    return Path.Combine( Path.GetTempPath(), String.Format( CultureInfo.InvariantCulture, "AR {0}.bmm", mapKey ) );
		}

	    protected virtual void OnAutoSaving( AutoSavingEventArgs e )
		{
			if ( AutoSaving != null )
			{
				AutoSaving( this, e );
			}
		}
	}
}